﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using EnvDTE;
using EnvDTE80;

namespace PropertyAddin
{
    /// <summary>
    /// Screen that displays found properties.
    /// </summary>
	public partial class PropertiesScreen : Form
	{
		#region Constants
		const string CTOR_NODE_TEXT = "ctor";
		const string REGION_CTOR_NAME = " Constructors";
		#endregion Constants

		#region Private Members
		private ClassManager m_Manager;
		private static bool hasLast = false;
		private static int lastHeight;
		private static int lastWidth;
		private static int lastX;
		private static int lastY;
		#endregion Private Members

		#region Control Members

        #endregion Control Members

        #region Constructors
        /// <summary>
        /// Default constructor
        /// </summary>
		public PropertiesScreen()
		{
			InitializeComponent();
		}
		#endregion Constructors

		#region Properties
		/// <summary>
		/// 
		/// </summary>
		public DTE2 ApplicationObject
		{
			get { return m_Manager.ApplicationObject; }
			set
			{
				m_Manager = new ClassManager(value);
				BindClasses();
			}
		}
		#endregion Properties

		#region Public Static Methods
		/// <summary>
		/// Displays these messages in a simple dialog box.
		/// </summary>
		/// <param name="messages"></param>
		public static void Debug(params string[] messages)
		{
			StringBuilder sb = new StringBuilder();

			foreach (string s in messages)
				sb.AppendLine(s);
			MessageBox.Show(sb.ToString(), "Properties Generator Debug");
		}
		#endregion Public Static Methods

		#region Events
		/// <summary>
		/// 
		/// </summary>
		/// <param name="e"></param>
		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);
			if (hasLast)
			{
				base.Top = lastY;
				base.Left = lastX;
				base.Width = lastWidth;
				base.Height = lastHeight;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void ClassForm_Closing(object sender, CancelEventArgs e)
		{
			hasLast = true;
			lastX = base.Left;
			lastY = base.Top;
			lastWidth = base.Width;
			lastHeight = base.Height;
			if (base.DialogResult == DialogResult.OK)
				this.ApplyChange();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void lnkAll_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
		{
			foreach (TreeNode node in this.treeProperties.Nodes)
				node.Checked = true;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void lnkAllGet_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
		{
			foreach (TreeNode parentNode in this.treeProperties.Nodes)
				foreach (TreeNode node in parentNode.Nodes)
					if (node.Tag.ToString() == "GET")
						node.Checked = true;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void lnkUnSet_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
		{
			foreach (TreeNode parentNode in this.treeProperties.Nodes)
				foreach (TreeNode node in parentNode.Nodes)
					if (node.Tag.ToString() == "SET")
						node.Checked = false;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void lnkUnAll_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
		{
			foreach (TreeNode node in this.treeProperties.Nodes)
				node.Checked = false;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void treeProperties_AfterCheck(object sender, TreeViewEventArgs e)
		{
			if (e.Node.Parent == null)
			{
				foreach (TreeNode node in e.Node.Nodes)
					node.Checked = e.Node.Checked;
			}
			else
			{
				ClassField tag = e.Node.Parent.Tag as ClassField;
				bool flag = e.Node.Checked;
				bool flag2 = false;
				bool flag3 = false;

				if (e.Node.Tag.ToString() == "GET")
				{
					flag3 = !tag.HasGetter && flag;
					flag2 = tag.HasGetter && !flag;
				}
				else if (e.Node.Tag.ToString() == "SET")
				{
					flag3 = !tag.HasSetter && flag;
					flag2 = tag.HasSetter && !flag;
				}
				if (flag3)
					e.Node.BackColor = Color.Yellow;
				else if (flag2)
					e.Node.BackColor = Color.Red;
				else
					e.Node.BackColor = Color.White;
			}
		}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void treeConstructor_AfterCheck(object sender, TreeViewEventArgs e)
        {
            if (e.Node.Parent == null)
            {
                foreach (TreeNode node in e.Node.Nodes)
                    node.Checked = e.Node.Checked;
            }
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void txtClasses_SelectedIndexChanged(object sender, EventArgs e)
		{
			this.BindTrees();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void lnkAllParam_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
		{
			foreach (TreeNode node in this.treeConstructor.Nodes)
				if (node.Text == CTOR_NODE_TEXT)
					foreach (TreeNode child in node.Nodes)
						child.Checked = true;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void lnkUnAllParam_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
		{
			foreach (TreeNode node in this.treeConstructor.Nodes)
				if (node.Text == CTOR_NODE_TEXT)
					foreach (TreeNode child in node.Nodes)
						child.Checked = false;
		}
		#endregion Events

		#region Private Methods
		/// <summary>
		/// Binds the combobox with class present in the active document.
		/// </summary>
		private void BindClasses()
		{
			cmbClasses.Items.Clear();
			foreach (IStruct stru in m_Manager.Classes)
				this.cmbClasses.Items.Add(stru.Name);

			if (this.cmbClasses.Items.Count > 0)
				this.cmbClasses.SelectedIndex = 0;
		}

		/// <summary>
		/// Binds the control tree with the current class.
		/// </summary>
		private void BindTrees()
		{
			try
			{
				IStruct _struct = m_Manager.Classes[this.cmbClasses.SelectedIndex];
				Dictionary<CodeElement, int> list;

				m_Manager.SelectClass(_struct);
				list = this.FormatCode();
				this.BindAutoConstructor(list);
				this.treeProperties.Nodes.Clear();

				foreach (ClassField cf in m_Manager.Fields)
				{
					TreeNode node;

					if (!cf.Variable.IsConstant)
					{
						StringBuilder sb = new StringBuilder();
						TreeNode nodeGet = new TreeNode(string.Concat("get: ", cf.PropertyName));
						TreeNode nodeSet = new TreeNode(string.Concat("set: ", cf.PropertyName));

						if (cf.Variable.IsShared)
							sb.Append("static ");
						if (cf.IsInherited)
							sb.Append("inherited ");
						sb.Append(cf.Name);
						sb.Append(": ");
						sb.Append(m_Manager.GetShortTypeName(cf.Variable.Type));
						node = new TreeNode(sb.ToString());

						nodeGet.Tag = "GET";
						nodeGet.Checked = cf.HasGetter;
						nodeSet.Tag = "SET";
						nodeSet.Checked = cf.HasSetter;
						node.Tag = cf;

						node.Checked = nodeGet.Checked && nodeSet.Checked;
						node.Nodes.Add(nodeGet);
						node.Nodes.Add(nodeSet);
						this.treeProperties.Nodes.Add(node);
						this.treeProperties.ExpandAll();
					}
				}
			}
			catch (Exception exception)
			{
				MessageBox.Show("Error Binding Tree: " + exception.Message);
				this.Close();
			}
		}

		/// <summary>
		/// Binds a node for the automatic constructor creation.
		/// </summary>
		/// <param name="list"></param>
		private void BindAutoConstructor(Dictionary<CodeElement, int> list)
		{
			TreeNode node;

			// Clear the constructor creation tree.
			this.treeConstructor.Nodes.Clear();

			// Populate the base part of the constructor tree.
			foreach (CodeElement element in m_Manager.CurrentStructure.Bases)
			{
				if (element.Name != "Object" && element.Name != "ValueType"
					&& element.Kind != vsCMElement.vsCMElementInterface)
				{
					ClassManager bcm = new ClassManager(element);

					node = new TreeNode(string.Concat("base: ", element.Name));
					this.treeConstructor.Nodes.Add(node);
					foreach (CodeElement elementCtor in bcm.GetConstructorsList().Keys)
					{
						CodeFunction constructor = elementCtor as CodeFunction;
						StringBuilder sb = new StringBuilder("(");
						TreeNode childNode;

						foreach (CodeElement elementParam in constructor.Parameters)
						{
							CodeParameter parameter = elementParam as CodeParameter;

							if (sb.Length > 1)
								sb.Append(", ");
							sb.Append(parameter.Name);
							sb.Append(": ");
							sb.Append(bcm.GetShortTypeName(parameter.Type));
						}

						sb.Append(")");
						childNode = new TreeNode(sb.ToString());
						childNode.Tag = constructor.Parameters;
						node.Nodes.Add(childNode);
					}
				}
			}

			// Populate the parameter part of the constructor tree.
			node = new TreeNode(CTOR_NODE_TEXT);
			this.treeConstructor.Nodes.Add(node);
			foreach (ClassField cf in m_Manager.Fields)
			{
				if (!cf.Variable.IsConstant && !cf.Variable.IsShared && !cf.IsInherited)
				{
					TreeNode childNode = new TreeNode(string.Concat(cf.ParameterName, ": ",
						m_Manager.GetShortTypeName(cf.Variable.Type)));

					childNode.Tag = cf;
					node.Nodes.Add(childNode);
				}
			}
			this.treeConstructor.ExpandAll();
		}

		/// <summary>
		/// Applies the changes according to the tree node check changes.
		/// </summary>
		private void ApplyChange()
		{
			this.ApplyChangeOnConstructorNode();
			foreach (TreeNode node in this.treeProperties.Nodes)
				this.ApplyChangeOnPropertyNode(node);
		}

		/// <summary>
		/// Creates the constructor according to the constructor tree check (and its children).
		/// </summary>
		private void ApplyChangeOnConstructorNode()
		{
			List<ConstructorParameter> paramList = new List<ConstructorParameter>();
			bool isChecked = false;
			int baseParametersCount = 0;

			foreach (TreeNode node in this.treeConstructor.Nodes)
			{
				if (node.Text == CTOR_NODE_TEXT)
				{
					isChecked = node.Checked;
					foreach (TreeNode childNode in node.Nodes)
					{
						ClassField cf = childNode.Tag as ClassField;

						if (childNode.Checked)
							paramList.Add(new ConstructorParameter(cf));
					}
				}
				else
				{
					foreach (TreeNode childNode in node.Nodes)
					{
						if (childNode.Checked)
						{
							CodeElements parameters = childNode.Tag as CodeElements;

							foreach (CodeElement element in parameters)
								paramList.Add(new ConstructorParameter(element));
							baseParametersCount = parameters.Count;
							break;
						}
					}
				}
			}

			if (isChecked)
			{
				try
				{
					object position = m_Manager.FindLastConstructor();
					bool isFirst = (position == null);
					CodeElement constructor;

					if (m_Manager.CurrentStructure is StructStructure && paramList.Count == 0)
						MessageBox.Show("A default constructor cannot be redefined for a struct.");
					else if (m_Manager.IsConstructorExisting(paramList))
						MessageBox.Show("A constructor with these parameters already exists.");
					else
					{
						if (isFirst)
							position = m_Manager.FindLastField();
						m_Manager.WriteConstructor(paramList, baseParametersCount, position);

						// Insert or move the constructors region.
						constructor = m_Manager.FindLastConstructor();
						if (isFirst)
							FrameByRegion(constructor, constructor, REGION_CTOR_NAME);
						else
							ClassManager.MoveEndRegion(constructor, REGION_CTOR_NAME);
					}
				}
				catch (Exception exception)
				{
					MessageBox.Show(string.Format("Error creating constructor for: \n{0}",
						exception.Message));
				}
			}
		}

		/// <summary>
		/// Applies the changes on the corresponding property according to that tree node check changes
		/// (and its chidren).
		/// </summary>
		/// <param name="node"></param>
		private void ApplyChangeOnPropertyNode(TreeNode node)
		{
			bool getChecked = true;
			bool setChecked = true;

			foreach (TreeNode node2 in node.Nodes)
			{
				string tag = node2.Tag as string;

				if (tag == "GET")
					getChecked = node2.Checked;
				else if (tag == "SET")
					setChecked = node2.Checked;
			}

			ClassField field = (ClassField)node.Tag;
			bool createGetter = !field.HasGetter && getChecked;
			bool createSetter = !field.HasSetter && setChecked;
			bool deleteGet = field.HasGetter && !getChecked;
			bool deleteSet = field.HasSetter && !setChecked;

			if (createGetter || createSetter)
			{
				try
				{
					object position = m_Manager.FindPreviousProperty(field);

					if (position == null)
						position = m_Manager.FindLastConstructor();
					if (position == null)
						position = m_Manager.FindLastField();
					m_Manager.WriteProperty(field, createGetter, createSetter, chkInternal.Checked,
						position, txtTemplateGet.Text, txtTemplateSet.Text);
				}
				catch (Exception exception)
				{
					MessageBox.Show(string.Format("Error creating get/set for: {0}\n{1}", field.Name,
						exception.Message));
				}
			}

			if ((deleteGet || deleteSet) && field.Property != null)
			{
				try
				{
					m_Manager.RemoveProperty(field, deleteGet, deleteSet);
				}
				catch (Exception exception2)
				{
					MessageBox.Show(string.Format("Error removing get/set for: {0}\n{1}", field.Name,
						exception2.Message));
				}
			}
		}

		/// <summary>
		/// Formats the code with regions for fields and constructors.
		/// </summary>
		private Dictionary<CodeElement, int> FormatCode()
		{
			const string REGION_FIELD_NAME = " {0} Members";
			Dictionary<CodeElement, int> list = m_Manager.GetFieldsList();
			Dictionary<string, int> counts = new Dictionary<string, int>();
			CodeElement first, last;

			if (list.Count > 0)
			{
				int spaceCount = this.ScanCodeElements(list, out first, out last, ref counts);

				if (spaceCount == 0)
				{
					string accessLabel = "Variable";

					// Build the variable members region name.
					if (counts.Count == 1)
						accessLabel = (new List<string>(counts.Keys))[0];
					FrameByRegion(first, last, string.Format(REGION_FIELD_NAME, accessLabel));
				}
			}

			list = m_Manager.GetConstructorsList();
			counts = null;
			if (list.Count > 0)
			{
				int spaceCount = this.ScanCodeElements(list, out first, out last, ref counts);

				if (spaceCount == 0)
					FrameByRegion(first, last, REGION_CTOR_NAME);
			}
			return list;
		}

		/// <summary>
		/// Scans that list of code elements to know if they are successive and if their access mode are
		/// the same or not. Returns also the first and last code elements in 2 output parameters.
		/// </summary>
		/// <param name="list"></param>
		/// <param name="first"></param>
		/// <param name="last"></param>
		/// <param name="counts"></param>
		/// <returns></returns>
		private int ScanCodeElements(Dictionary<CodeElement, int> list, out CodeElement first,
			out CodeElement last, ref Dictionary<string, int> counts)
		{
			int spaceCount = 0, index = 0;

			first = null;
			last = null;
			foreach (KeyValuePair<CodeElement, int> entry in list)
			{
				last = entry.Key;
				if (first != null && index < entry.Value - 1)
					++spaceCount;
				if (first == null)
					first = last;
				index = entry.Value;
				if (counts != null)
				{
					switch (m_Manager[last.Name].Variable.Access)
					{
						case vsCMAccess.vsCMAccessPrivate:
							IncrementCount(counts, "Private");
							break;
						case vsCMAccess.vsCMAccessProtected:
							IncrementCount(counts, "Protected");
							break;
						case vsCMAccess.vsCMAccessPublic:
							IncrementCount(counts, "Public");
							break;
						default:
							++spaceCount;
							break;
					}
				}
			}
			return spaceCount;
		}

		/// <summary>
		/// Increments the count associated with that label in that counts dictionary.
		/// </summary>
		/// <param name="counts"></param>
		/// <param name="label"></param>
		private static void IncrementCount(Dictionary<string, int> counts, string label)
		{
			int count;

			if (counts.TryGetValue(label, out count))
				counts[label] = count + 1;
			else
				counts.Add(label, 1);
		}

		/// <summary>
		/// Inserts the beginning of region before that first code element and the end of region after
		/// that last code element with that region name.
		/// </summary>
		/// <param name="first"></param>
		/// <param name="last"></param>
		/// <param name="regionName"></param>
		private static void FrameByRegion(CodeElement first, CodeElement last, string regionName)
		{
			EditPoint point = first.StartPoint.CreateEditPoint(), point1, point2;
			string indentation = ClassManager.GetIndentation(point);

			point = ClassManager.GetCommentStartPoint(point);
			point1 = ClassManager.Find(point, ClassManager.REGION_START, true);
			point2 = ClassManager.Find(point, ClassManager.REGION_STOP, true);
			if (point1 == null || point2 != null && point1.LessThan(point2))
			{
				bool isStartOfLine = false;

				// Test if the insertion point is at the start of the line.
				point1 = point.CreateEditPoint();
				point1.StartOfLine();
				isStartOfLine = point.EqualTo(point1);

				// Insert the beginning of the variable members region.
				if (isStartOfLine)
					point.Insert(indentation);
				point.Insert(ClassManager.REGION_START);
				point.Insert(regionName);
				point.Insert(Environment.NewLine);
				if (!isStartOfLine)
					point.Insert(indentation);

				// Insert the end of the variable members region.
				point = last.EndPoint.CreateEditPoint();
				point.Insert(Environment.NewLine);
				point.Insert(indentation);
				point.Insert(ClassManager.REGION_STOP);
				point.Insert(regionName);
			}
		}
		#endregion Private Methods
	}
}
